/*
 * Project: Sudoku Explainer
 * Copyright (C) 2006-2007 Nicolas Juillerat
 * 
 * Project: Sudoku Hint Engine for Android
 * Copyright (C) 2010-2013 Emanuel Moecklin
 * 
 * Available under the terms of the Lesser General Public License (LGPL)
 */
package com.onegravity.sudoku.solver.rules.chaining;

import java.util.ArrayList;
import java.util.Arrays;
import java.util.BitSet;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;

import android.net.Uri;

import com.onegravity.sudoku.solver.HintResource;
import com.onegravity.sudoku.solver.HintType;
import com.onegravity.sudoku.solver.core.Potential;
import com.onegravity.sudoku.solver.data.Cell;
import com.onegravity.sudoku.solver.data.ImmutableCell;
import com.onegravity.sudoku.solver.util.BitSetFactory;
import com.onegravity.sudoku.solver.util.HtmlLoader;
import com.onegravity.sudoku.solver.util.Link;

/**
 * - Nishio Forcing Chain
 * - Dynamic Double Forcing Chain
 * - Dynamic Contradiction Forcing Chain hint.
 * Consist of two forcing chains.
 */
public class BinaryForcingChainHint extends ChainHint {

	private static final long serialVersionUID = 6282850228196647292L;
	
	private final Potential srcPotential;
    private final Potential dstOnPotential;
    private final Potential dstOffPotential;
    private final boolean isAbsurd;
    private final boolean isNishio;

    // Cache
    private int _complexity = -1;

    public BinaryForcingChainHint(ChainMetaInfo metaInfo, Map<Cell, BitSet> removablePotentials,
            Potential srcPotential, Potential fromOnPotential, Potential fromOffPotential,
            boolean isAbsurd, boolean isNishio) {
        super(metaInfo, removablePotentials, true, true);
        this.srcPotential = srcPotential;
        this.dstOnPotential = fromOnPotential;
        this.dstOffPotential = fromOffPotential;
        this.isAbsurd = isAbsurd;
        this.isNishio = isNishio;
    }

    @Override
    public boolean isWorth() {
    	boolean isWorth = isNishio ? mMetaInfo.mConfig.useNishioForcingChains() :
    					  isAbsurd ? mMetaInfo.mConfig.useContradictionForcingChains() :
    					  mMetaInfo.mConfig.useDoubleForcingChains();
    	return isWorth && super.isWorth();
    }

    @Override
    public int getFlatViewCount() {
        return 2;
    }

    @Override
    public Collection<Cell> getSelectedCells(HintType type) {
    	if (type==HintType.SMALL_CLUE) return Collections.singletonList(srcPotential.cell);
        return Arrays.asList(new Cell[] {srcPotential.cell, dstOnPotential.cell});
    }

    @Override
    public Map<Cell, BitSet> getSelectedPotentials(HintType type, int viewNum) {
    	Map<Cell, BitSet> result = null;
    	if (type==HintType.BIG_CLUE) {
    		Map<Cell, BitSet> tmp = getColorPotentials(this.dstOnPotential, true, true, false);
    		result = getColorPotentials(this.dstOffPotential, true, true, true);
    		result.putAll(tmp);
    		result.remove(srcPotential.cell);	// show source in on/off colors green/yellow)
    		result.remove(dstOnPotential.cell);	// show target in on/off colors green/yellow)
    	}
		return result;
    }
    
    @Override
    public Map<Cell, BitSet> getAddedPotentials(HintType type, int viewNum) {
    	Map<Cell, BitSet> result = new HashMap<Cell, BitSet>();
    	Potential targetPotential = (viewNum == 0 ? this.dstOnPotential : this.dstOffPotential);
    	switch (type) {
    	case BIG_CLUE:
    		if (targetPotential.isOn) result.put(targetPotential.cell, BitSetFactory.createOneElement(targetPotential.value));
    	case SMALL_CLUE:
    		if (srcPotential.isOn) result.put(srcPotential.cell, BitSetFactory.createOneElement(srcPotential.value));
    		break;
    	case HINT: {
            result = (viewNum >= getFlatViewCount()) ?
            		 super.getNestedAddedPotentials(type, viewNum) :
            		 getColorPotentials(targetPotential, true, false);
    	}
    	}
        return result;
    }
    
    @Override
    public Map<Cell, BitSet> getSecondarySelectedPotentials(HintType type, int viewNum) {
        if (viewNum >= getFlatViewCount()) return super.getNestedSecondarySelectedPotentials(type, viewNum);

    	Map<Cell, BitSet> result = new HashMap<Cell, BitSet>();
        Potential targetPotential = (viewNum == 0 ? this.dstOnPotential : this.dstOffPotential);
        switch (type) {
    	case BIG_CLUE:
    		if (!targetPotential.isOn) result.put(targetPotential.cell, BitSetFactory.createOneElement(targetPotential.value));
    	case SMALL_CLUE:
    		if (!srcPotential.isOn) result.put(srcPotential.cell, BitSetFactory.createOneElement(srcPotential.value));
    		break;
    	case HINT: {
            result = (viewNum >= getFlatViewCount()) ?
            		 super.getNestedAddedPotentials(type, viewNum) :
            		 getColorPotentials(targetPotential, false, false);
    	}
    	}

    	return result;
    }

    @Override
    public Map<ImmutableCell, BitSet> getRemovedPotentials(HintType type) {
        return (type==HintType.HINT) ? super.getRemovedPotentials(type) : null;
    }
    
    @Override
    public Collection<Link> getLinks(HintType type, int viewNum) {
        if (viewNum >= getFlatViewCount()) return super.getNestedLinks(type, viewNum);
        Potential start = (viewNum == 0 ? this.dstOnPotential : this.dstOffPotential);
        return (type==HintType.HINT) ? getLinks(start) : null;
    }

    @Override
    protected Collection<Potential> getChainsTargets() {
        Collection<Potential> result = new ArrayList<Potential>(2);
        result.add(this.dstOnPotential);
        result.add(this.dstOffPotential);
        return result;
    }

    @Override
    protected Potential getChainTarget(int viewNum) {
    	return (viewNum == 0) ? this.dstOnPotential : this.dstOffPotential;
    }

    @Override
    public int getFlatComplexity() {
        if (_complexity < 0) {
            _complexity = getAncestorCount(dstOnPotential) + getAncestorCount(dstOffPotential);
        }
        return _complexity;
    }

    @Override
    public int getChainingSortKey() {
        if (isAbsurd) return 7; // After all reductions
        else return 1;
    }

    public boolean isAbsurd() {
        return this.isAbsurd;
    }

    @Override
    public double getDifficulty() {
        return mMetaInfo.getDifficulty() + getLengthDifficulty();
    }

     @Override
    protected Potential getResult() {
        return (isNishio || isAbsurd) ? new Potential(srcPotential.cell, srcPotential.value, !srcPotential.isOn) : dstOnPotential;
    }

    @Override
    public String getHintHtml(HintType type) {
    	HintResource hintRes = isNishio ? HintResource.NISHIO_FORCING_CHAIN :
    						   isAbsurd ? HintResource.DYNAMIC_CONTRADICTION_CHAIN :
    						   HintResource.DYNAMIC_DOUBLE_FORCING_CHAIN; 
		String result = HtmlLoader.loadHtml(hintRes.hint(type));
		Potential srcOn = new Potential(srcPotential.cell, srcPotential.value, true);
		Potential srcOff = new Potential(srcPotential.cell, srcPotential.value, false);
		Potential srcReverse = new Potential(srcPotential.cell, srcPotential.value, !srcPotential.isOn);
		String chainOn = getHtmlChain(dstOnPotential);
		String chainOff = getHtmlChain(dstOffPotential);
		if (isAbsurd) {
			// Nishio Chains, Dynamic Contradiction Forcing Chains
			result = HtmlLoader.format(result, srcPotential.toWeakString(), dstOnPotential.toStrongString(),
									   dstOffPotential.toStrongString(), srcReverse.toStrongString(), chainOn, chainOff,
									   Cell.toString(srcPotential.cell), srcPotential.value, Cell.toString(dstOnPotential.cell));
		}
		else {
			// Dynamic Double Forcing Chain
			result = HtmlLoader.format(result, srcOn.toWeakString(), srcOff.toWeakString(),
									   dstOnPotential.toStrongString(), chainOn, chainOff,
									   Cell.toString(srcPotential.cell), srcPotential.value, Cell.toString(dstOnPotential.cell));
		}
		return super.appendNestedChainsDetails(result);
    }

    @Override
    public String getName() {
        String name = isNishio ? HintResource.NISHIO_FORCING_CHAIN.getName() :
        			  isAbsurd ? HintResource.DYNAMIC_CONTRADICTION_CHAIN.getName() :
        			  HintResource.DYNAMIC_DOUBLE_FORCING_CHAIN.getName();
        return name + super.getNameSuffix();
    }

    @Override
    public Uri getLink() {
        return isNishio ? HintResource.NISHIO_FORCING_CHAIN.getUrl() :
			   isAbsurd ? HintResource.DYNAMIC_CONTRADICTION_CHAIN.getUrl() :
			   HintResource.DYNAMIC_DOUBLE_FORCING_CHAIN.getUrl();
	}

    @Override
    public String toString() {
        return isNishio ? HintResource.NISHIO_FORCING_CHAIN.toString() + ": " + srcPotential.toString() + (srcPotential.isOn ? " on" : " off") + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
                    	  " ==> " + this.dstOffPotential.toString() + " both on & off" : //$NON-NLS-1$ //$NON-NLS-2$
               isAbsurd ? HintResource.DYNAMIC_CONTRADICTION_CHAIN.toString() + ": " + srcPotential.toString() + (srcPotential.isOn ? " on" : " off") + //$NON-NLS-1$ //$NON-NLS-2$ //$NON-NLS-3$
            		   	  " ==> " + this.dstOffPotential.toString() + " both on & off" : //$NON-NLS-1$ //$NON-NLS-2$
            		   	  HintResource.DYNAMIC_DOUBLE_FORCING_CHAIN.toString() + ": " + srcPotential.toString() + " on & off ==> " //$NON-NLS-1$ //$NON-NLS-2$
            		   	  + this.dstOnPotential.toString() + (dstOnPotential.isOn ? " on" : " off"); //$NON-NLS-1$ //$NON-NLS-2$
    }

}